package com.ccc.boot.security.util;

import com.ccc.boot.exception.BusinessException;
import com.ccc.boot.response.ResponseCode;
import com.ccc.boot.security.config.RsaKeyProperties;
import com.ccc.boot.security.constant.AuthConstant;
import com.ccc.boot.security.pojo.AuthUserData;
import com.ccc.boot.security.pojo.AuthorityData;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.security.SignatureException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
 * 获取当前登录用户信息
 *
 * @author superC
 * @version 0.1.0
 * @date 2022/3/27
 */
@Component
public class AuthUtils {

    protected static final Logger logger = LoggerFactory.getLogger(AuthUtils.class);

    @Autowired
    private RsaKeyProperties rsaKeyProperties;

    /**
     * 私钥加密生成token
     *
     * @param userInfo 载荷中的数据
     * @return token
     */
    public String generateToken(AuthUserData userInfo) {
        return JwtUtils.generateToken(userInfo, rsaKeyProperties.getPrivateKey());
    }

    /**
     * 私钥加密生成token
     *
     * @param userInfo 载荷中的数据
     * @param expire   过期时间，单位分钟
     * @return token
     */
    public String generateTokenExpireInMinutes(AuthUserData userInfo, int expire) {
        return JwtUtils.generateTokenExpireInMinutes(userInfo, rsaKeyProperties.getPrivateKey(), expire);
    }

    /**
     * 刷新token
     *
     * @param token
     * @return {@link String}
     * @date 2022/4/11 16:05
     * @author superC
     */
    public String refreshToken(String token) {
        if (token == null) {
            throw new BusinessException(ResponseCode.TOKEN_ISNULL_ERROR);
        }
        if (isAccessTokenExpired(token)) {
            throw new BusinessException(ResponseCode.TOKEN_EXPIRED_ERROR);
        }
        AuthUserData authUserData = getLoginUser(token);
        if (authUserData == null) {
            throw new BusinessException(ResponseCode.PARSE_TOKEN_ERROR);
        }
        return generateToken(authUserData);
    }

    /**
     * 获取请求头中的token
     */
    public String getToken(HttpServletRequest request) {
        String header = request.getHeader(AuthConstant.TOKEN_HEADER);
        // 验证header是否存在Authorization
        if (StringUtils.isBlank(header)) {
            throw new BusinessException(ResponseCode.TOKEN_ISNULL_ERROR);
        }
        // 验证token前缀
        if (!header.startsWith(AuthConstant.TOKEN_BEARER)) {
            throw new BusinessException(ResponseCode.TOKEN_PREFIX_ERROR);
        }
        return header.substring(AuthConstant.TOKEN_BEARER.length());
    }

    /**
     * 验证token是否过期
     *
     * @param token
     * @return true 表示token已过期
     */
    public Boolean isAccessTokenExpired(String token) {
        try {
            return JwtUtils.isAccessTokenExpired(token, rsaKeyProperties.getPublicKey());
        } catch (ExpiredJwtException e) {
            return true;
        }
    }

    /**
     * 获取当前登录用户信息
     */
    public AuthUserData getLoginUser(UserDetails userDetails) {
        AuthUserData data = new AuthUserData();
        data.setUsername(userDetails.getUsername());
        Collection<AuthorityData> authorities = (Collection<AuthorityData>) userDetails.getAuthorities();
        Set<AuthorityData> roles = new HashSet<>(authorities);
        data.setRoles(roles);
        return data;
    }

    /**
     * 根据token，获取当前登录用户信息
     */
    public AuthUserData getLoginUser(HttpServletRequest request) {
        return JwtUtils.getInfoFromToken(getToken(request), rsaKeyProperties.getPublicKey(), AuthUserData.class).getUserInfo();
    }

    /**
     * 根据token，获取当前登录用户信息
     */
    public AuthUserData getLoginUser(String token) {
        AuthUserData user = null;
        try {
            // 获取认证用户信息
            user = JwtUtils.getInfoFromToken(token, rsaKeyProperties.getPublicKey(), AuthUserData.class).getUserInfo();
        } catch (ExpiredJwtException e) {
            throw new BusinessException(ResponseCode.TOKEN_EXPIRED_ERROR);
        } catch (SignatureException e) {
            throw new BusinessException(ResponseCode.TOKEN_SIGNATURE_ERROR);
        } catch (IllegalArgumentException e) {
            throw new BusinessException(ResponseCode.TOKEN_ISNULL_ERROR);
        } catch (Exception e) {
            throw new BusinessException(ResponseCode.PARSE_TOKEN_ERROR);
        }
        return user;
    }

    /**
     * 获取当前登录用户信息
     */
    public AuthUserData getLoginUser() {
        AuthUserData user = new AuthUserData();
        // 获取认证信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        user.setUsername(authentication.getName());
        Collection<AuthorityData> authorities = (Collection<AuthorityData>) authentication.getAuthorities();
        user.setRoles(new HashSet<>(authorities));
        return user;
    }

    /**
     * 获取当前登录用户信息
     */
    public String getUsername() {
        // 获取认证信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return authentication.getName();
    }

    /**
     * 在Security上下文存储用户信息
     *      认证通过后将身份信息放入SecurityContextHolder上下文，SecurityContext与当前线程进行绑定，方便获取用户身份。
     *
     * @param request
     * @param user
     */
    public void storageAuthentication(HttpServletRequest request, AuthUserData user) {
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

}